package epub

import (
	"archive/zip"
	"encoding/xml"
	"fmt"
	"io"
	"strings"

	"gitee.com/xgotool/epub/model"
)

// 书籍初始化结构体信息
type EpubBook struct {
	Path    string          `json:"path"` // 书籍文件路径
	Meta    *EpubMeta       `json:"meta"` // 书籍元数据
	Ncx     model.NCX       `json:"-"`    // 书籍目录文件
	zipInfo *zip.ReadCloser // 书籍zip文件信息
	Menu    []EpubMenu      `json:"menu"` // 书籍目录结构
}

type EpubMenu struct {
	Id    string `json:"id"`    // 目录id
	Title string `json:"title"` // 目录标题
	Link  string `json:"link"`  // 目录链接
}

// 书籍源数据信息
type EpubMeta struct {
	Mimetype    string `json:"mimetype"`    // 书籍mimetype类型
	Title       string `json:"title"`       // 书籍标题
	Author      string `json:"author"`      // 书籍作者
	Language    string `json:"language"`    // 书籍语言
	Description string `json:"description"` // 书籍描述
	Publisher   string `json:"publisher"`   // 书籍出版社
	Date        string `json:"date"`        // 书籍日期
	RootOpf     string `json:"root_opf"`    // 书籍根目录文件名
	Uuid        string `json:"uuid"`        // 书籍唯一标识
	Ncx         string `json:"ncx"`         // 书籍目录文件名
}

// 获取epub书籍的mimetype类型
func (e *EpubBook) Mimetype() string {
	if e.Meta == nil {
		return "application/epub+zip"
	}
	return e.Meta.Mimetype
}

// 加载epub书籍内容
func (e *EpubBook) loadFile() error {
	r, err := zip.OpenReader(e.Path)
	if err != nil {
		return err
	}
	e.zipInfo = r
	e.Meta = &EpubMeta{
		Mimetype: "application/epub+zip",
	}
	e.Ncx = model.NCX{}
	// 解析 mimtype 文件
	if mt, err := e.readFile("mimetype"); err == nil {
		e.Meta.Mimetype = mt
	}
	// 解析 META-INF/container.xml 文件，获取目录文件名
	if rootOpf, err := e.readFile("META-INF/container.xml"); err == nil {
		ro := model.Container{}
		if err := xml.Unmarshal([]byte(rootOpf), &ro); err == nil {
			for j := 0; j < len(ro.RootFiles); j++ {
				if ro.RootFiles[j].MediaType == "application/oebps-package+xml" {
					e.Meta.RootOpf = ro.RootFiles[j].FullPath
					break
				}
			}
		}
	}
	// 解析目录文件名
	if menus, err := e.readFile(e.Meta.RootOpf); err == nil {
		mod := model.Opf{}
		if err := xml.Unmarshal([]byte(menus), &mod); err == nil {
			e.Meta.Title = mod.Metadata.Title
			e.Meta.Author = mod.Metadata.Creator
			e.Meta.Language = mod.Metadata.Language
			e.Meta.Description = mod.Metadata.Description
			e.Meta.Uuid = mod.Metadata.Identifier.UUID
			for i := 0; i < len(mod.Manifest.Items); i++ {
				if mod.Manifest.Items[i].ID == "ncx" {
					e.Meta.Ncx = mod.Manifest.Items[i].Href
					// 部分文件中的 toc.ncx 会省略掉路径，所以此处需要追加路径
					if !strings.Contains(e.Meta.Ncx, "/") {
						// 省略掉了路径
						for tmp := 0; tmp < len(e.zipInfo.File); tmp++ {
							if strings.HasSuffix(e.zipInfo.File[tmp].Name, e.Meta.Ncx) {
								e.Meta.Ncx = e.zipInfo.File[tmp].Name
								break
							}
						}
					}
					break
				}
			}
		}
	}
	// 解析目录文件
	if menus, err := e.readFile(e.Meta.Ncx); err == nil {
		if err := xml.Unmarshal([]byte(menus), &e.Ncx); err != nil {
			return fmt.Errorf("目录解析失败：%w", err)
		}
		for i := 0; i < len(e.Ncx.NavMap.NavPoints); i++ {
			e.Menu = append(e.Menu, EpubMenu{
				Title: e.Ncx.NavMap.NavPoints[i].NavLabel.Text,
				Link:  e.Ncx.NavMap.NavPoints[i].Content.Src,
				Id:    e.Ncx.NavMap.NavPoints[i].ID,
			})
		}
	}
	return nil
}

// 关闭EPUB书籍
func (e *EpubBook) Close() {
	if e.zipInfo != nil {
		e.zipInfo.Close()
	}
}

// 读取 epub 书籍文件中指定文件内容
//
//	path	文件路径
func (e *EpubBook) readFile(path string) (string, error) {
	byt, err := e.ReadFile(path)
	if err != nil {
		return "", err
	}
	return string(byt), nil
}

// 对外公开的 EPUB 内容阅读接口
//
//	path	epub文件路径
func (e *EpubBook) ReadFile(path string) ([]byte, error) {
	if e.zipInfo == nil {
		return nil, fmt.Errorf("epub文件未加载")
	}
	if path == "" {
		return nil, fmt.Errorf("文件路径不能为空")
	}
	for i := 0; i < len(e.zipInfo.File); i++ {
		if e.zipInfo.File[i].Name == path {
			// 打开zip内的此文件
			fs, err := e.zipInfo.File[i].Open()
			if err != nil {
				return nil, err
			}
			defer fs.Close()
			// 读取文件内容
			return io.ReadAll(fs)
		}
	}
	// 如果没找到完全匹配的文件的话，就尝试找后缀相同的文件
	for i := 0; i < len(e.zipInfo.File); i++ {
		if strings.HasSuffix(e.zipInfo.File[i].Name, path) {
			// 打开zip内的此文件
			fs, err := e.zipInfo.File[i].Open()
			if err != nil {
				return nil, err
			}
			defer fs.Close()
			// 读取文件内容
			return io.ReadAll(fs)
		}
	}
	return nil, nil
}
